﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

using Microsoft.Scripting.Ast;
using Microsoft.Scripting.Utils;

namespace DlrBasic.Runtime
{
    /// <summary>
    /// Contains user function. The ones that where defined using 
    /// DEF FNxx statement. Represented as Delegate and can be called 
    /// from CLR.
    /// </summary>
    public sealed class DlrBasicUserFunctions
    {
        Dictionary<string, Delegate> userFunctions = new Dictionary<string,Delegate>(StringComparer.InvariantCultureIgnoreCase);

        public void SetFunction(string name, Delegate function)
        {
            userFunctions[name] = function;
        }

        public Delegate GetFunction(string name)
        {
            return userFunctions[name];
        }

        public object Invoke(string name, object[] parameters)
        {
            Delegate function = GetFunction(name);
            MethodInfo method = function.Method;
            ParameterInfo[] methodParameter = method.GetParameters();
            object[] convertedParameters = new object[parameters.Length];
            for (int i = 0; i < parameters.Length; i++)
            {                
                convertedParameters[i] = ConvertTo(methodParameter[i + 1].ParameterType, 
                    parameters[i]);
            }
            object result = function.DynamicInvoke(convertedParameters);
            return result;
        }

        private static object ConvertTo(Type targetType, object value)
        {
            if (value == null) throw new ArgumentNullException("value");

            if(targetType == value.GetType()) return value;

            if (value.GetType() == typeof(string) || targetType == typeof(string))
            {
                throw new DlrBasicException(Properties.Resources.TypeMismatchException);
            }

            return Convert.ChangeType(value, targetType);
        }

        public Func<TResult> GetFunction<TResult>(string name)
        {
            return (Func<TResult>)GetFunction(name);
        }

        public Func<T1, TResult> GetFunction<T1, TResult>(string name)
        {
            return (Func<T1, TResult>)GetFunction(name);
        }
    }
}
